package com.cartel.genius.dao.impl;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;

import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Restrictions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import com.cartel.genius.dao.PlaceDAO;
import com.cartel.genius.dao.exceptions.CategoryNotFoundException;
import com.cartel.genius.dao.exceptions.PlaceExistsException;
import com.cartel.genius.dao.exceptions.PlaceNotFoundException;
import com.cartel.genius.dao.exceptions.PlaceParentNotFoundException;
import com.cartel.genius.dto.CategoryTO;
import com.cartel.genius.dto.PlaceTO;
import com.cartel.genius.entities.CategoryEntity;
import com.cartel.genius.entities.PlaceEntity;
import com.cartel.genius.util.BeanTransformer;
import com.mysql.jdbc.StringUtils;

/**
 * DAO implementation class for Place operations
 * 
 * @author Naresh Reddy
 */
public class PlaceDAOImpl extends HibernateDaoSupport implements PlaceDAO {
	
	protected static final Logger logger = LoggerFactory.getLogger(PlaceDAOImpl.class);
	
	@Override
	public PlaceTO addRootPlace(PlaceTO placeTO) throws PlaceExistsException, CategoryNotFoundException {
		logger.debug("Entered [addRootPlace] of [PlaceDAOImpl]");
		
		// check if root place already exists or not
		// It is assuming there will multiple root level places like countries
		// To support only for one country then uncomment below validation
		
		/*DetachedCriteria rootPlaceCriteria = DetachedCriteria.forClass(PlaceEntity.class);
		rootPlaceCriteria.add(Restrictions.eq("status", 1));
		if (getHibernateTemplate().findByCriteria(rootPlaceCriteria).size() > 0) {
			logger.warn("Root place already exists in the system");
			throw new PlaceExistsException("Root place already exists in the system");
		}*/
		
		if (!isValidPlaceCode(placeTO.getPlaceCode())) {
			logger.warn("Place Code " + placeTO.getPlaceCode() + " already associated with some other place");
			throw new PlaceExistsException("Place Code " + placeTO.getPlaceCode() + " already associated with some other place");
		}
		
		List<CategoryEntity> categoryList = null;
		
		if (placeTO.getCategoryTO() != null) {
			categoryList = getCategoryEntities(placeTO.getCategoryTO().getCategoryId(), placeTO.getCategoryTO().getCategoryName());
		} else {
			logger.warn("Category is not present");
			throw new CategoryNotFoundException("Category is not present");
		}
		
		PlaceEntity rootPlace = BeanTransformer.transformPlace(placeTO);
		rootPlace.setCategory(categoryList.get(0));
		rootPlace.setCreatedDate(Calendar.getInstance().getTimeInMillis());
		rootPlace.setModifiedDate(Calendar.getInstance().getTimeInMillis());
		
		Integer savedRootPlaceId = (Integer) getHibernateTemplate().save(rootPlace);
		
		// again add the same place Id as parent
		PlaceEntity savedRootEntity = getHibernateTemplate().get(PlaceEntity.class, savedRootPlaceId);
		savedRootEntity.setParent(savedRootEntity);
		savedRootEntity.setModifiedDate(Calendar.getInstance().getTimeInMillis());
		getHibernateTemplate().saveOrUpdate(savedRootEntity);
		
		rootPlace.setPlaceId(savedRootPlaceId);
		logger.debug("Root place " + rootPlace + " saved successfully in the system");
		
		logger.debug("Exiting [addRootPlace] of [PlaceDAOImpl]");
		return BeanTransformer.transformPlace(rootPlace);
	}

	@SuppressWarnings("unchecked")
	private List<CategoryEntity> getCategoryEntities(int categoryId, String categoryName) throws CategoryNotFoundException {
		DetachedCriteria categoryCriteria = DetachedCriteria.forClass(CategoryEntity.class);
		
		boolean categoryFlag = false;
		if (categoryId != 0) {
			categoryCriteria.add(Restrictions.idEq(categoryId));
			categoryFlag = true;
		}
		
		if (!StringUtils.isNullOrEmpty(categoryName)) {
			categoryCriteria.add(Restrictions.like("categoryName", categoryName));
			categoryFlag = true;
		}
		
		List<CategoryEntity> categoryList = null;
		
		if (categoryFlag) {
			categoryList = getHibernateTemplate().findByCriteria(categoryCriteria);
			
			if (categoryList == null || categoryList.size() == 0) {
				logger.warn("Category not found in the system");
				throw new CategoryNotFoundException("Category not found in the system");
			}
		} else {
			logger.warn("Category is not present");
			throw new CategoryNotFoundException("Category is not present");
		}
		return categoryList;
	}
	
	@SuppressWarnings("unchecked")
	private List<PlaceEntity> getPlaceEntities(int placeId, String placeCode) throws PlaceNotFoundException {
		DetachedCriteria placeCriteria = DetachedCriteria.forClass(PlaceEntity.class);
		
		boolean placeFlag = false;
		if (placeId != 0) {
			placeCriteria.add(Restrictions.idEq(placeId));
			placeFlag = true;
		}
		
		if (!StringUtils.isNullOrEmpty(placeCode)) {
			placeCriteria.add(Restrictions.like("placeCode", placeCode));
			placeFlag = true;
		}
		
		List<PlaceEntity> placeList = null;
		
		if (placeFlag) {
			placeList = getHibernateTemplate().findByCriteria(placeCriteria);
			
			if (placeList == null || placeList.size() == 0) {
				logger.warn("Place not found in the system");
				throw new PlaceNotFoundException("Place not found in the system");
			}
		} else {
			logger.warn("Place is not present");
			throw new PlaceNotFoundException("Place is not present");
		}
		return placeList;
	}
	
	/**
	 * Method to check whether a place code exists in the system of not
	 * 
	 * @param placeCode
	 * @return boolean
	 */
	private boolean isValidPlaceCode(String placeCode) {
		logger.debug("Entered [isValidPlaceCode] of [PlaceDAOImpl]");
		
		DetachedCriteria placeCodeCriteria = DetachedCriteria.forClass(PlaceEntity.class);
		placeCodeCriteria.add(Restrictions.eq("placeCode", placeCode));
		placeCodeCriteria.add(Restrictions.eq("status", 1));
		
		if (getHibernateTemplate().findByCriteria(placeCodeCriteria).size() > 0) {
			return false;
		}
		
		return true;
	}		

	@Override
	public PlaceTO addPlace(PlaceTO placeTO) throws PlaceExistsException, PlaceParentNotFoundException, CategoryNotFoundException {
		logger.debug("Entered [addPlace] of [PlaceDAOImpl]");
		
		if (!isValidPlaceCode(placeTO.getPlaceCode())) {
			logger.warn("Place Code " + placeTO.getPlaceCode() + " already associated with some other place");
			throw new PlaceExistsException("Place Code " + placeTO.getPlaceCode() + " already associated with some other place");
		}
		
		// Get Category Entity and throw exception if category is not valid
		List<CategoryEntity> categoryList = null;
		
		if (placeTO.getCategoryTO() != null) {
			categoryList = getCategoryEntities(placeTO.getCategoryTO().getCategoryId(), placeTO.getCategoryTO().getCategoryName());
		} else {
			logger.warn("Category is not present");
			throw new CategoryNotFoundException("Category is not present");
		}
		
		// Get Parent Place Entity and throw exception if parent is not valid
		List<PlaceEntity> parentPlaceList;
		try {
			parentPlaceList = getPlaceEntities(placeTO.getParentPlaceId(), placeTO.getParentPlaceCode());
		} catch (PlaceNotFoundException e) {
			logger.warn("Parent place not found in the system");
			throw new PlaceParentNotFoundException(e);
		}
		 
		PlaceEntity placeEntity = BeanTransformer.transformPlace(placeTO);
		placeEntity.setParent(parentPlaceList.get(0));
		placeEntity.setCategory(categoryList.get(0));
		placeEntity.setCreatedDate(Calendar.getInstance().getTimeInMillis());
		placeEntity.setModifiedDate(Calendar.getInstance().getTimeInMillis());
		
		Integer savedPlaceId = (Integer) getHibernateTemplate().save(placeEntity);
		
		placeEntity.setPlaceId(savedPlaceId);
		
		logger.debug("Exiting [addPlace] of [PlaceDAOImpl]");
		
		return BeanTransformer.transformPlace(placeEntity);
	}

	@Override
	public List<PlaceTO> addPlaces(List<PlaceTO> places, PlaceTO parentPlace) throws PlaceParentNotFoundException, CategoryNotFoundException {
		logger.debug("Entered [addPlaces] of [PlaceDAOImpl]");
		
		// Get Parent Place Entity and throw exception if parent is not valid
		List<PlaceEntity> parentPlaceList;
		try {
			parentPlaceList = getPlaceEntities(parentPlace.getPlaceId(), parentPlace.getPlaceCode());
		} catch (PlaceNotFoundException e) {
			logger.warn("Parent place not found in the system");
			throw new PlaceParentNotFoundException(e);
		}
		
		List<PlaceTO> insertedPlaceList = new ArrayList<PlaceTO>();
		List<PlaceTO> failedPlaceList = new ArrayList<PlaceTO>();
		
		for (PlaceTO placeTO : places) {
			
			if (!isValidPlaceCode(placeTO.getPlaceCode())) {
				logger.warn("Place Code " + placeTO.getPlaceCode() + " already associated with some other place");
				failedPlaceList.add(placeTO);
				continue;
			}
			
			// Get Category Entity and throw exception if category is not valid
			List<CategoryEntity> categoryList = null;
			
			if (placeTO.getCategoryTO() != null) {
				categoryList = getCategoryEntities(placeTO.getCategoryTO().getCategoryId(), placeTO.getCategoryTO().getCategoryName());
			} else {
				logger.warn("Category is not present");
				failedPlaceList.add(placeTO);
				continue;
			}
			
			PlaceEntity placeEntity = BeanTransformer.transformPlace(placeTO);
			placeEntity.setParent(parentPlaceList.get(0));
			placeEntity.setCategory(categoryList.get(0));
			placeEntity.setCreatedDate(Calendar.getInstance().getTimeInMillis());
			placeEntity.setModifiedDate(Calendar.getInstance().getTimeInMillis());
			
			Integer savedPlaceId = (Integer) getHibernateTemplate().save(placeEntity);
			
			placeEntity.setPlaceId(savedPlaceId);
			
			insertedPlaceList.add(BeanTransformer.transformPlace(placeEntity));			
		}
		
		logger.info("Total " + insertedPlaceList.size() + " places inserted successfully among " + places.size() + " places");
		
		logger.debug("Exiting [addPlaces] of [PlaceDAOImpl]");
		
		return insertedPlaceList;
	}	

	@Override
	public void removePlace(PlaceTO place) throws PlaceNotFoundException {
		logger.debug("Entered [removePlace] of [PlaceDAOImpl]");
		
		// Get Place Entity and throw exception if parent is not valid
		List<PlaceEntity> placeList = getPlaceEntities(place.getPlaceId(), place.getPlaceCode());
		
		PlaceEntity placeEntity = placeList.get(0);
		placeEntity.setStatus(0);
		placeEntity.setModifiedDate(Calendar.getInstance().getTimeInMillis());
		
		getHibernateTemplate().saveOrUpdate(placeEntity);
		
		logger.debug("Exiting [removePlace] of [PlaceDAOImpl]");		
	}
	
	@Override
	public void removePlace(int placeId) throws PlaceNotFoundException {
		logger.debug("Entered [removePlace] of [PlaceDAOImpl]");
		
		PlaceEntity placeEntity = getHibernateTemplate().get(PlaceEntity.class, placeId);
		
		if (placeEntity == null) {
			logger.warn("Place with Id " + placeId + " doesn't exists in the system");
			throw new PlaceNotFoundException("Place with Id " + placeId + " doesn't exists in the system");
		}
		
		placeEntity.setStatus(0);
		placeEntity.setModifiedDate(Calendar.getInstance().getTimeInMillis());
		
		getHibernateTemplate().saveOrUpdate(placeEntity);
		logger.debug("Exiting [removePlace] of [PlaceDAOImpl]");		
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public PlaceTO modifyPlace(PlaceTO oldPlace, PlaceTO newPlace) throws PlaceExistsException, PlaceNotFoundException, CategoryNotFoundException {
		logger.debug("Entered [modifyPlace] of [PlaceDAOImpl]");
		
		List<PlaceEntity> oldPlaceList = getPlaceEntities(oldPlace.getPlaceId(), oldPlace.getPlaceCode());
		
		PlaceEntity placeEntity = oldPlaceList.get(0);
		
		if (!StringUtils.isNullOrEmpty(newPlace.getPlaceCode())) {
			if (!isValidPlaceCode(newPlace.getPlaceCode())) {
				logger.warn("Place Code " + newPlace.getPlaceCode() + " already associated with some other place");
				throw new PlaceExistsException("Place Code " + newPlace.getPlaceCode() + " already associated with some other place");
			}
			
			placeEntity.setPlaceCode(newPlace.getPlaceCode());
		}
		
		if (!StringUtils.isNullOrEmpty(newPlace.getPlaceName())) {
			placeEntity.setPlaceName(newPlace.getPlaceName());
		}
		
		if (newPlace.getCategoryTO() != null) {
			CategoryEntity categoryEntity = placeEntity.getCategory();
			
			if (newPlace.getCategoryTO().getCategoryId() != 0) {
				if (categoryEntity.getCategoryId() != newPlace.getCategoryTO().getCategoryId()) {
					categoryEntity = getHibernateTemplate().get(CategoryEntity.class, newPlace.getCategoryTO().getCategoryId());
				}
			} else {
				if (!categoryEntity.getCategoryName().equalsIgnoreCase(newPlace.getCategoryTO().getCategoryName())) {
					DetachedCriteria categoryCriteria = DetachedCriteria.forClass(CategoryEntity.class);
					categoryCriteria.add(Restrictions.eq("categoryName", newPlace.getCategoryTO().getCategoryName()));
					
					List<CategoryEntity> categoryList = getHibernateTemplate().findByCriteria(categoryCriteria);
					
					if (categoryList == null || categoryList.size() == 0) {
						logger.warn("Category " + newPlace.getCategoryTO() + " doesn't exist in the system");
						throw new CategoryNotFoundException("Category " + newPlace.getCategoryTO() + " doesn't exist in the system");
					}
					
					categoryEntity = categoryList.get(0);
				}
			}
			
			placeEntity.setCategory(categoryEntity);
		}
		
		placeEntity.setModifiedDate(Calendar.getInstance().getTimeInMillis());		
		getHibernateTemplate().saveOrUpdate(placeEntity);
		
		logger.debug("Exiting [modifyPlace] of [PlaceDAOImpl]");
		
		return BeanTransformer.transformPlace(placeEntity);
	}

	@Override
	public PlaceTO modifyPlaceParent(int placeId, int parentPlaceId) throws PlaceNotFoundException {
		logger.debug("Entered [modifyPlaceParent] of [PlaceDAOImpl]");
		
		PlaceEntity placeEntity = getHibernateTemplate().get(PlaceEntity.class, placeId);
		
		if (placeEntity == null) {
			logger.warn("Place with Id " + placeId + " doesn't exist in the system");
			throw new PlaceNotFoundException("Place with Id " + placeId + " doesn't exist in the system");
		}
		
		PlaceEntity parentPlaceEntity = getHibernateTemplate().get(PlaceEntity.class, parentPlaceId);
		
		if (parentPlaceEntity == null) {
			logger.warn("Place with Id " + parentPlaceId + " doesn't exist in the system");
			throw new PlaceNotFoundException("Place with Id " + parentPlaceId + " doesn't exist in the system");
		}
		
		placeEntity.setParent(parentPlaceEntity);
		placeEntity.setModifiedDate(Calendar.getInstance().getTimeInMillis());
		getHibernateTemplate().saveOrUpdate(placeEntity);
		
		logger.debug("Exiting [modifyPlaceParent] of [PlaceDAOImpl]");
		
		return BeanTransformer.transformPlace(placeEntity);
	}
	
	@Override
	public PlaceTO modifyPlaceParent(PlaceTO childPlace, PlaceTO parentPlace) throws PlaceNotFoundException {
		logger.debug("Entered [modifyPlaceParent] of [PlaceDAOImpl]");
		
		List<PlaceEntity> childPlaceList = getPlaceEntities(childPlace.getPlaceId(), childPlace.getPlaceCode());
		
		List<PlaceEntity> parentPlaceList = getPlaceEntities(parentPlace.getPlaceId(), parentPlace.getPlaceCode());
		
		PlaceEntity placeEntity = childPlaceList.get(0);
		placeEntity.setParent(parentPlaceList.get(0));
		placeEntity.setModifiedDate(Calendar.getInstance().getTimeInMillis());
		
		getHibernateTemplate().saveOrUpdate(placeEntity);
		
		logger.debug("Exiting [modifyPlaceParent] of [PlaceDAOImpl]");
		
		return BeanTransformer.transformPlace(placeEntity);
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<PlaceTO> getPlaces(PlaceTO placeTO, CategoryTO categoryTO) throws PlaceNotFoundException, CategoryNotFoundException {
		logger.debug("Entered [getPlaces] of [PlaceDAOImpl]");
		
		CategoryTO placeCategory = placeTO.getCategoryTO();
		
		if (placeTO.getCategoryTO() == null) {
			placeCategory = getPlaceCategory(placeTO);
		}
		
		List<CategoryEntity> categoryHierarchy = getCategoryHierarchy(placeCategory.getCategoryId(), placeCategory.getCategoryName(), categoryTO);
		PlaceEntity placeEntity = getPlaceEntities(placeTO.getPlaceId(), placeTO.getPlaceCode()).get(0);
		
		DetachedCriteria placeCriteria = DetachedCriteria.forClass(PlaceEntity.class);
		placeCriteria.add(Restrictions.eq("parent.placeId", placeEntity.getPlaceId()));
		placeCriteria.add(Restrictions.eq("category.categoryId", categoryHierarchy.get(0).getCategoryId()));
		placeCriteria.add(Restrictions.eq("status", 1));
		
		List<PlaceEntity> placesList = getHibernateTemplate().findByCriteria(placeCriteria);
		categoryHierarchy.remove(0);
		
		for (CategoryEntity categoryEntity : categoryHierarchy) {
			List<PlaceEntity> tmpList = placesList;
			placesList = new ArrayList<PlaceEntity>();
			for (PlaceEntity pEntity : tmpList) {
				placeCriteria.add(Restrictions.eq("parent.placeId", pEntity.getPlaceId()));
				placeCriteria.add(Restrictions.eq("category.categoryId", categoryEntity.getCategoryId()));
				placeCriteria.add(Restrictions.eq("status", 1));
				
				placesList.addAll(getHibernateTemplate().findByCriteria(placeCriteria));
			}						
		}
		
		logger.debug("Exiting [getPlaces] of [PlaceDAOImpl]");
		
		return BeanTransformer.transformPlace(placesList);
	}
	
	@SuppressWarnings("unchecked")
	private CategoryTO getPlaceCategory(PlaceTO placeTO) throws PlaceNotFoundException {
		logger.debug("Entered [getPlaceCategory] of [PlaceDAOImpl]");
		
		DetachedCriteria placeCriteria = DetachedCriteria.forClass(PlaceEntity.class);
		boolean placeFlag = false;
		
		if (placeTO.getPlaceId() != 0) {
			placeCriteria.add(Restrictions.idEq(placeTO.getPlaceId()));
			placeFlag = true;
		}
		
		if (!StringUtils.isNullOrEmpty(placeTO.getPlaceCode())) {
			placeCriteria.add(Restrictions.eq("placeCode", placeTO.getPlaceCode()));
			placeFlag = true;
		}
		
		if (!placeFlag) {
			logger.warn("Invalid place data");
			throw new PlaceNotFoundException("Invalid place data");
		}
		
		List<PlaceEntity> placeEntities = getHibernateTemplate().findByCriteria(placeCriteria); 
		
		if (placeEntities == null || placeEntities.size() == 0) {
			logger.warn("Place not found in the system");
			throw new PlaceNotFoundException("Place not found in the system");
		}
		
		PlaceEntity pe = placeEntities.get(0);
		logger.debug("Exiting [getPlaceCategory] of [PlaceDAOImpl]");
		
		return BeanTransformer.transformCategory(pe.getCategory());
	}

	/**
	 * Method to get category hierarchy
	 * 
	 * @param fromCategoryId
	 * @param toCategoryId
	 * @return List<CategoryEntity>
	 * @throws CategoryNotFoundException
	 */
	private List<CategoryEntity> getCategoryHierarchy(int fromCategoryId, String fromCategoryName, CategoryTO categoryTO) throws CategoryNotFoundException {
		
		CategoryEntity fromCE = getCategoryEntities(fromCategoryId, fromCategoryName).get(0);
		
		if (fromCE == null) {
			logger.warn("Category not found in the system");
			throw new CategoryNotFoundException("Category not found in the system");
		}
		
		CategoryEntity toCE = getCategoryEntities(categoryTO.getCategoryId(), categoryTO.getCategoryName()).get(0);
		
		if (toCE == null) {
			logger.warn("Category not found in the system");
			throw new CategoryNotFoundException("Category not found in the system");
		}
		
		List<CategoryEntity> categoryHierarchy = new ArrayList<CategoryEntity>();
		int toCategoryId = toCE.getCategoryId();
		
		while (toCategoryId != fromCategoryId) {
			CategoryEntity ce = getHibernateTemplate().get(CategoryEntity.class, toCategoryId);
			
			categoryHierarchy.add(ce);
			toCategoryId = ce.getParentCategory().getCategoryId();
		}		
		Collections.reverse(categoryHierarchy);
		
		return categoryHierarchy;
	}

}
